#include "CogParameters.h"

// ------------------------------------------------------------------------------------------

bool CogParameters::assign(const string& label, const string& value)
{
	string lclabel;
	for (size_t i = 0; i < label.size(); i++)
	{
		lclabel.push_back(tolower(label[i]));
	}
	bool okay = true;
	if (lclabel == toLower("buildMode"))
	{
		map<string, uint32_t> options;
		options["parental"] = COGBM_PARENTAL;
		options["continuous"] = COGBM_CONTINUOUS;
		options["continuous_bigstart"] = COGBM_CONTINUOUS | COGBM_BIGSTART;
		okay &= convertChoice(label, value, buildMode, options);
		if (okay) buildModeString = value;
	}
	else
	if (lclabel == toLower("elitePop"))
	{
		okay &= convertToInt(lclabel, value, elitePop);
	}
	else
	if (lclabel == toLower("evalThreads"))
	{
		okay &= convertToInt(lclabel, value, evalThreads);
	}
	else
	if (lclabel == toLower("groupMode"))
	{
		map<string, uint32_t> options;
		options["none"] = COGGM_NONE;
		options["novel"] = COGGM_NOVELTY_LOCAL;
		options["novelroot"] = COGGM_NOVELTY_ELEVATED;
		okay &= convertChoice(label, value, groupMode, options);
		if (okay) groupModeString = value;
	}
	else
	if (lclabel == toLower("maxEpochs"))
	{
		okay &= convertToInt(lclabel, value, maxEpochs);
	}
	else
	if (lclabel == toLower("maxPop"))
	{
		okay &= convertToInt(lclabel, value, maxPop);
		if (okay && (maxPop <= 0))
		{
			globalMessageLog.print(COGMT_ERROR_PARAMETER, VERBOSITY_NONE, "%s: %d is invalid; must be greater than zero", label.c_str(), maxPop);
			okay = false;
		}
	}
	else 
	if (lclabel.substr(0,7) == toLower("measure"))
	{
		CogEvaluator* pEval = assignObjective(label, value, 0); // rank 0 for measures
		if (pEval)
		{
			CogCacheMode cm_arg;
			if (lclabel == toLower("measure&value"))
			{
				cm_arg = COGCM_VALUE;
			}
			else 
			if (lclabel == toLower("measure&stack"))
			{
				cm_arg = COGCM_STACK;
			}
			else
			{
				if (lclabel != toLower("measure"))
				{
					globalMessageLog.print(COGMT_ERROR_PARAMETER, VERBOSITY_NONE, "%s: %s unknown; assuming you mean \"measure\"", label.c_str(), lclabel.c_str());
				}
				cm_arg = COGCM_OFF;
			}
			pEval->setCacheMode(cm_arg);
			okay &= true;
		}
		else
		{
			okay = false;
		}
	}
	else
	if (lclabel.substr(0,9) == toLower("objective"))
	{
		size_t tagsize;
		CogCacheMode cm_arg;
		if (lclabel.substr(0,15) == toLower("objective&value"))
		{
			tagsize = string("objective&value").size();
			cm_arg = COGCM_VALUE;
		}
		else
		if (lclabel.substr(0,15) == toLower("objective&stack"))
		{
			tagsize = string("objective&stack").size();
			cm_arg = COGCM_STACK;
		}
		else
		{
			tagsize = string("objective").size();
			cm_arg = COGCM_OFF;
		}
		if ((lclabel.size() > tagsize+2) && (lclabel[tagsize] == '<') && (lclabel[label.size()-1] == '>'))
		{
			uint32_t rank;
			if (convertToInt(label, lclabel.substr(tagsize+1, label.size()-1), rank))
			{
				if ((rank > 0) && (rank < OBJECTIVE_RANK_MAX))
				{
					CogEvaluator* pEval = assignObjective(label, value, rank); // start at rank 1 (not 0, as 0 is for non-objective measures)
					if (pEval)
					{
						pEval->setCacheMode(cm_arg);
						okay &= true;
					}
					else
					{
						okay = false;
					}
				}
				else
				{
					globalMessageLog.print(COGMT_ERROR_PARAMETER, VERBOSITY_NONE, "%s: %d is not a valid objective rank; must be in range [1,%d)", label.c_str(), rank, OBJECTIVE_RANK_MAX);
					okay = false;
				}
			}
			else
			{
				globalMessageLog.print(COGMT_ERROR_PARAMETER, VERBOSITY_NONE, "%s: %s is not a valid objective rank; must be an integer", label.c_str(), lclabel.substr(tagsize).c_str());
				okay = false;
			}
		}
		else
		{
			globalMessageLog.print(COGMT_INFO_PARAMETER, VERBOSITY_HIGH, "%s: missing rank specifier (e.g. objective<1> = ...); assuming rank 1", label.c_str());
			CogEvaluator* pEval = assignObjective(label, value, 1);
			if (pEval)
			{
				pEval->setCacheMode(cm_arg);
				okay &= true;
			}
			else
			{
				okay = false;
			}
		}
	}
	else
	if (lclabel == toLower("probDrawFromTemplate"))
	{
		okay &= convertToFloat(lclabel, value, probDrawFromTemplate);
		if (okay && ((probDrawFromTemplate < 0.0) || (probDrawFromTemplate > 1.0)))
		{
			globalMessageLog.print(COGMT_ERROR_PARAMETER, VERBOSITY_NONE, "%s: %f is outside of valid range [0-1]", label.c_str(), probDrawFromTemplate);
			okay = false;
		}
	}
	else
	if (lclabel == toLower("probReuseOnCreate"))
	{
		okay &= convertToFloat(lclabel, value, probReuseOnCreate);
		if (okay && ((probReuseOnCreate < 0.0) || (probReuseOnCreate > 1.0)))
		{
			globalMessageLog.print(COGMT_ERROR_PARAMETER, VERBOSITY_NONE, "%s: %f is outside of valid range [0-1]", label.c_str(), probReuseOnCreate);
			okay = false;
		}
	}
	else
	if (lclabel == toLower("randomSeed"))
	{
		okay &= convertToInt(lclabel, value, randomSeed);
	}
	else
	if (lclabel == toLower("tournamentSize"))
	{
		okay &= convertToInt(lclabel, value, tourSize);
	}
	else
	if (lclabel == toLower("sizeLimit"))
	{
		okay &= convertToInt(lclabel, value, sizeLimit);
		if (okay && (sizeLimit <= 0))
		{
			globalMessageLog.print(COGMT_ERROR_PARAMETER, VERBOSITY_NONE, "%s: %d is invalid; must be greater than zero", label.c_str(), sizeLimit);
			okay = false;
		}
	}
	else
	if (lclabel == toLower("stopCondition"))
	{
		map<string, uint32_t> options;
		options["none"] = COGSC_NONE;
		options["primaryzero"] = COGSC_PRIMARYZERO;
		okay &= convertChoice(label, value, stopCondition, options);
		if (okay) stopConditionString = value;
	}
	else
	if (lclabel == toLower("verbosity"))
	{
		map<string, uint32_t> options;
		options["silent"] = VERBOSITY_NONE;
		options["low"] = VERBOSITY_LOW;
		options["normal"] = VERBOSITY_NORMAL;
		options["high"] = VERBOSITY_HIGH;
		options["max"] = VERBOSITY_MAX;
		okay &= convertChoice(label, value, verbosity, options);
		if (okay) 
		{
			globalMessageLog.setMinVerbosity(verbosity);
			verbosityString = value;
		}
	}
	else
	{
		globalMessageLog.print(COGMT_ERROR_PARAMETER, VERBOSITY_NONE, "Unknown parameter: %s", label.c_str());
		okay = false;
	}
	return okay;
}

CogEvaluator* CogParameters::assignObjective(const string& label, const string& value, uint32_t rank)
{
	bool founds = false;
	for (size_t i = 0; i < value.size(); i++)
	{
		if (value[i] == '<')
		{
			founds = true;
			if (value[value.size()-1] == '>')
			{
				return assignObjective(label, value.substr(0, i), value.substr(i+1), rank);
			}
			else
			{
				globalMessageLog.print(COGMT_ERROR_PARAMETER, VERBOSITY_NONE, "%s: %s is missing a closing parameter delimiter (>)", label.c_str(), value.c_str());
			}
			break;
		}
	}
	if (!founds) // no '<' found
	{
		return assignObjective(label, value, "", rank);
	}
	return NULL;
}

CogEvaluator* CogParameters::assignObjective(const string& label, const string& objlabel, const string& objparam, uint32_t rank)
{
	CogObjective obj;
	CogEvaluator* pEval = NULL;
	if (pEval = obj.create(objlabel, objparam, rank))
	{
		objectives.addObjective(obj);
	}
	else
	{
		globalMessageLog.print(COGMT_ERROR_PARAMETER, VERBOSITY_NONE, "%s: %s<%s> is not a valid objective specification", label.c_str(), objlabel.c_str(), objparam.c_str());
	}
	return pEval;
}

bool CogParameters::convertToBool(const string& label, const string& value, bool& convert)
{
	string lcvalue = toLower(value);
	if ((lcvalue == "1")
		|| (lcvalue == "on")
		|| (lcvalue == "true")
		|| (lcvalue == "yes"))
	{
		convert = true;
		return true;
	}
	else
	if (((lcvalue == "0")
		|| (lcvalue == "off")
		|| (lcvalue == "false")
		|| (lcvalue == "no")))
	{
		convert = false;
		return true;
	}
	else
	{
		globalMessageLog.print(COGMT_ERROR_PARAMETER, VERBOSITY_NONE, "%s: \"%s\" must be either true or false", label.c_str(), value.c_str());
		return false;
	}
}

bool CogParameters::convertToInt(const string& label, const string& value, uint32_t& convert)
{
	convert = atol(value.c_str());
	if (!convert)
	{
		if (value[0] != '0')
		{
			globalMessageLog.print(COGMT_ERROR_PARAMETER, VERBOSITY_NONE, "%s: \"%s\" does not appear to be an integer", label.c_str(), value.c_str());
			return false;
		}
	}
	return true;
}

bool CogParameters::convertChoice(const string& label, const string& value, uint32_t& convert, const map<string, uint32_t>& options)
{
	string lcvalue = toLower(value);
	for (map<string, uint32_t>::const_iterator opt_iter = options.begin(); opt_iter != options.end(); ++opt_iter)
	{
		if (lcvalue == toLower(opt_iter->first))
		{
			convert = opt_iter->second;
			return true;
		}
	}
	ostringstream strStream;
	strStream << label.c_str() << " \"" << value.c_str() << "\" is not a valid choice for this parameter\n";
	strStream << "-- valid choices are:";
	for (map<string, uint32_t>::const_iterator opt_iter = options.begin(); opt_iter != options.end(); ++opt_iter)
	{
		strStream << opt_iter->first.c_str() << "\n";
	}
	globalMessageLog.printStream(COGMT_ERROR_PARAMETER, VERBOSITY_NONE, strStream);
	return false;
}

bool CogParameters::convertToFloat(const string& label, const string& value, double& convert)
{
	convert = atof(value.c_str());
	if (convert == 0.0f)
	{
		if (value[0] != '0')
		{
			globalMessageLog.print(COGMT_ERROR_PARAMETER, VERBOSITY_NONE, "%s: \"%s\" does not appear to be a float", label.c_str(), value.c_str());
			return false;
		}
	}
	return true;
}

void CogParameters::reset()
{
	buildMode = COGBM_PARENTAL;
	buildModeString = "parental";
	elitePop = 1;
	evalThreads = 1;
	groupMode = COGGM_NONE;
	groupModeString = "none";
	maxEpochs = 0;
	maxPop = 1;
	objectives.clear();
	probDrawFromTemplate = 0.0;
	probReuseOnCreate = 0.0;
	randomSeed = static_cast<uint32_t>(time(NULL));
	sizeLimit = 10000;
	stopCondition = COGSC_NONE;
	stopConditionString = "none";
	tourSize = 2;
	verbosity = VERBOSITY_NORMAL;
	verbosityString = "normal";
}

void CogParameters::toStream(ostream &out)
{
	out << "% -- Parameters --" << endl;
	objectives.toStream(out);
	out << "@ " << "buildMode" << " = " << buildModeString.c_str() << endl;
	out << "@ " << "elitePop" << " = " << elitePop << endl;
	out << "@ " << "evalThreads" << " = " << evalThreads << endl;
	//out << "@ " << "groupMode" << " = " << groupModeString << endl;
	out << "@ " << "maxEpochs" << " = " << maxEpochs << endl;
	out << "@ " << "maxPop" << " = " << maxPop << endl;
	out << "@ " << "probDrawFromTemplate" << " = " << probDrawFromTemplate << endl;
	out << "@ " << "probReuseOnCreate" << " = " << probReuseOnCreate << endl;
	out << "@ " << "randomSeed" << " = " << randomSeed << endl;
	out << "@ " << "sizeLimit" << " = " << sizeLimit << endl;
	out << "@ " << "stopCondition" << " = " << stopConditionString.c_str() << endl;
	out << "@ " << "tourSize" << " = " << tourSize << endl;
	out << "@ " << "verbosity" << " = " << verbosityString.c_str() << endl;
}

// -----

bool CogParameters::fromStream(istream &in)
{
	bool success = true;
	while (in.good())
	{
		// retrieve a parameter line from the input stream
		string paraString;
		getline(in, paraString, '\n');
		if (!paraString.size())
		{
			// empty line
			continue;
		}
		size_t index = 0;
		string label;
		string value;
		// skip initial space
		while ((paraString[index] == ' ') || (paraString[index] == '\t') || (paraString[index] == '\r'))
		{
			index++;
			if (index >= paraString.size())
			{
				// empty line
				continue;
			}
		}
		// locate parameter symbol (@)
		bool hasComment = false;
		if (paraString[index] != '@') 
		{
			// comment
			
			if (paraString[index] == '%') 
			{
				hasComment = true;
				
			}
			continue;
		}
		if (!hasComment)
		{
			index++;
			// look ahead for a potential in-line comment
			size_t paraLength = index;
			bool exclusion = false;
			while (paraLength != paraString.size())
			{
				if (paraString[paraLength] == '<') 
				{
					exclusion = true;
				}
				else
				if (paraString[paraLength] == '>') 
				{
					exclusion = false;
				}
				else
				if ((paraString[paraLength] == '%') && !exclusion)
				{
					hasComment = true;
					break;
				}
				paraLength++;
			}
			// skip subsequent spaces
			while ((paraString[index] == ' ') || (paraString[index] == '\t') || (paraString[index] == '\r'))
			{
				index++;
				if (index >= paraLength)
				{
					// blank parameter
					globalMessageLog.print(COGMT_ERROR_PARAMETER, VERBOSITY_NONE, "Parameter definition is empty: %s", paraString.c_str());
					return false;
				}
			}
			// find =
			size_t coldex = index;
			while (paraString[coldex] != '=')
			{
				coldex++;
				if (coldex >= paraLength)
				{
					// no label : value separator
					globalMessageLog.print(COGMT_ERROR_PARAMETER, VERBOSITY_NONE, "Missing \"=\" between parameter label and value: %s", paraString.c_str());
					return false;
				}
			}
			// find end point of label
			size_t endex = coldex-1;
			while ((paraString[endex] == ' ') || (paraString[endex] == '\t') || (paraString[endex] == '\r') || (paraString[endex] == '\n'))
			{
				endex--;
			}
			// copy label
			while (index != endex+1)
			{
				label.push_back(paraString[index++]);
			}
			index = coldex+1;
			// skip further space
			while ((paraString[index] == ' ') || (paraString[index] == '\t') || (paraString[index] == '\r') || (paraString[index] == '\n'))
			{
				index++;
				if (index >= paraLength)
				{
					// parameter without value
					globalMessageLog.print(COGMT_ERROR_PARAMETER, VERBOSITY_NONE, "Parameter has no value: %s", paraString.c_str());
					return false;
				}
			}
			// find end-of-line
			endex = paraLength-1;
			while ((paraString[endex] == ' ') || (paraString[endex] == '\t') || (paraString[endex] == '\r') || (paraString[endex] == '\n'))
			{
				endex--;
			}
			// copy value
			while (index != endex+1)
			{
				value.push_back(paraString[index++]);
			}
			success &= assign(label, value);
		}
		if (hasComment)
		{
			string comment;
			while (++index < paraString.size())
			{
				if (paraString[index])
				{
					comment.push_back(paraString[index]);
				}
			}
			if (comment.size())
			{
				globalMessageLog.print(COGMT_INFO_PARAMETER, VERBOSITY_NORMAL, "Parameter file comment: %s", comment.c_str());
			}
		}
	}
	return success;
}

// ------------------------------------------------------------------------------------------